<template>
	<div class="scroll-number">
		<template v-for="(val, index) in numbers">
			<div
				class="digit"
				:class="{ hide: val == '*' }"
				:style="itemStyle"
				v-if="!isNumber(val)"
				:key="'char-' + index"
			>
				<span>{{ val }}</span>
			</div>

			<ScrollNumberItem
				:key="getIndex(numbers, index)"
				v-else
				ref="scrollItem"
				:direction="direction"
				:transitionTime="transitionTime"
				:itemStyle="itemStyle"
			/>
		</template>
	</div>
</template>

<script>
/**
 * ScrollNumber
 * @prop {number|string} value input number value
 * @prop {Boolean} numberOnly whether allow number input only
 * @prop {number} transitionTime animation transition time
 * @prop {object} itemStyle css style of each number item
 *
 * @member {Promise<number>} process a promise instance of animation process
 * @method changeTo(value):Promise trigger to change the value
 *
 * @event change(value) emit when animation done
 */
import ScrollNumberItem, { DIRECTIONS } from './scroll-number-item';
import { getOptions } from './options';

const isNumber = (val) => !Number.isNaN(+val);

export default {
	name: 'ScrollNumber',
	components: {
		ScrollNumberItem,
	},
	props: {
		value: {
			type: [Number, String],
			default: 0,
		},
		numberOnly: {
			type: Boolean,
			default: () => getOptions().numberOnly,
		},
		transitionTime: {
			type: Number,
			default: () => getOptions().transitionTime,
		},
		itemStyle: Object,
	},
	data() {
		return {
			innerValue: this.value,
			isNumber,
			direction: DIRECTIONS.FORWARD,

			process: Promise.resolve(),
		};
	},
	computed: {
		numbers() {
			return this.getNumbers(this.innerValue);
		},
	},
	watch: {
		value(newV) {
			this.changeTo(newV);
		},
		innerValue(newV, oldV) {
			let compareFn;
			if (isNumber(this.value)) {
				compareFn = (a, b) => Math.abs(newV) > Math.abs(oldV);
			} else {
				compareFn = (a, b) => newV > oldV;
			}

			if (compareFn(newV, oldV)) {
				this.direction = DIRECTIONS.FORWARD;
			} else {
				this.direction = DIRECTIONS.BACKWARD;
			}
		},
	},
	mounted() {
		this.changeTo(this.innerValue);
	},
	methods: {
		changeTo(value) {
			if (this.numberOnly && !isNumber(value)) {
				console.warn('[vue-scroll-number]: You can only change value to a number');
				return Promise.reject();
			}
			this.process = this.process.then(() => {
				return new Promise((resolve) => {
					this.innerValue = value;
					setTimeout(() => {
						const promises = this.getNumbers(value)
							.filter(isNumber)
							.map((item, index) => {
								const scrollItem = this.$refs.scrollItem[index];
								if (scrollItem) {
									return scrollItem.changeTo(item);
								} else {
									return Promise.resolve();
								}
							});
						resolve(Promise.all(promises).then(() => (this.$emit('change', value), value)));
					});
				});
			});
			return this.process;
		},

		getNumbers(value) {
			let list = String(value)
				.split('')
				.map((it) => (isNumber(it) ? Number(it) : it));
			/* 补充一个* 是为了数据对齐 */
			return [...list, '*'];
		},

		// get index
		// except the non-number chars
		getIndex(numbers, index) {
			let nonNumCount = 0;
			for (let i = 0; i < index; i++) {
				if (!isNumber(numbers[i])) {
					nonNumCount++;
				}
			}
			return index - nonNumCount;
		},
	},
};

export { DIRECTIONS };
</script>
<style lang="less">
.scroll-number {
	display: inline-block;
	font-size: 0;
	.digit {
		display: inline-block;
		vertical-align: text-bottom;
		font-size: 14px;
		text-align: center;
		&.hide {
			opacity: 0;
			width: 0;
		}
	}
}

.scroll-number-item {
	display: inline-block;
	position: relative;
	width: 15px;
	height: 30px;
	overflow: hidden;

	.scroll-list {
		position: absolute;
		width: 100%;
		transition: transform 0.8s ease;
		ul {
			margin: 0;
			padding: 0;
			list-style: none;
			margin-block-start: 0;
			margin-block-end: 0;
			padding-inline-start: 0;
		}
	}
	.number-item {
		text-align: center;
		font-size: 14px;
	}
}
</style>
